package merisis.traitspace.impl;

import java.util.HashMap;
import java.util.Map;

import merisis.traitspace.HistoricalHierarchy;
import merisis.util.BitUtil;

import org.apache.solr.util.OpenBitSet;


public class HistoricalHierarchyImpl implements HistoricalHierarchy {

	private Map<Long, OpenBitSet> footprintsMap;
	private Map<Long, Integer> imap;
	private Map<Long, Integer> lmap;

	private OpenBitSet points;

	public HistoricalHierarchyImpl(Map<Long, OpenBitSet> footprintsMap,
			OpenBitSet points) {

		this.footprintsMap = footprintsMap;
		this.points = points;
	}

	public long[] roll(int step) {
		
		return BitUtil.toArray(rollHistory(step));
	}

	public OpenBitSet rollHistory(int step) {
		if (step < 0) {
			initIRank();
			return line(imap, -step - 1);
		} else {
			initLRank();
			return line(lmap, step);
		}
	}

	public long[] focused() {
		return BitUtil.toArray(points);
	}

	public void intersect(HistoricalHierarchy hh) {

		imap = null;
		lmap = null;

		HistoricalHierarchyImpl hhi = (HistoricalHierarchyImpl) hh;
		mergeFootprints(hhi);
		this.points.and(hhi.points);
	}

	public void union(HistoricalHierarchy hh) {
		imap = null;
		lmap = null;

		HistoricalHierarchyImpl hhi = (HistoricalHierarchyImpl) hh;
		mergeFootprints(hhi);
		this.points.or(hhi.points);
	}
	
	public void exclude(HistoricalHierarchy hh) {
		imap = null;
		lmap = null;

		HistoricalHierarchyImpl hhi = (HistoricalHierarchyImpl) hh;
		mergeFootprints(hhi);
		this.points.andNot(hhi.points);
	}
	
	private void mergeFootprints(HistoricalHierarchyImpl hhi) {
		
		for (Long k : hhi.footprintsMap.keySet()) {
			OpenBitSet s = footprintsMap.get(k);
			if (s == null)
				footprintsMap.put(k, hhi.footprintsMap.get(k));
			else
				s.or(hhi.footprintsMap.get(k));
		}
	}

	private void initIRank() {
		if (imap != null)
			return;

		imap = new HashMap<Long, Integer>();
		for (long point = points.nextSetBit(0); point >= 0; point = points
				.nextSetBit(point + 1)) {

			iRank(point);
		}
	}

	private void initLRank() {

		if (lmap != null)
			return;

		lmap = new HashMap<Long, Integer>();
		if (points == null)
			return;
		
		for (long point = points.nextSetBit(0); point >= 0; point = points
				.nextSetBit(point + 1)) {
			lRank(points.nextSetBit(point));
		}
	}

	private OpenBitSet line(Map<Long, Integer> m, int step) {

		OpenBitSet s = new OpenBitSet();
		if (points == null)
			return s;
		
		for (long point = points.nextSetBit(0); point >= 0; point = points
				.nextSetBit(point + 1)) {

			if (m.get(point) == step)
				s.set(point);
		}

		if (s.isEmpty())
			return null;

		return s;
	}

	private void iRank(long pt) {

		int pp = -1;
		OpenBitSet footprints = footprintsMap.get(pt);
		for (long p = footprints.nextSetBit(0); p >= 0; p = footprints
				.nextSetBit(p + 1)) {

			if (p == pt)
				continue;

			if (!points.get(p))
				continue;

			Integer level = imap.get(p);
			if (level == null) {
				iRank(p);
				level = imap.get(p);
			}

			if (level > pp)
				pp = level;
		}
		imap.put(pt, pp + 1);
	}

	private void lRank(long pt) {

		int pp = -1;
		for (long p = points.nextSetBit(0); p >= 0; p = points
				.nextSetBit(p + 1)) {

			if (p == pt)
				continue;

			if (footprintsMap.get(p).get(pt)) {
				Integer level = lmap.get(p);
				if (level == null) {
					lRank(p);
					level = lmap.get(p);
				}

				if (level > pp)
					pp = level;
			}
		}
		lmap.put(pt, pp + 1);
	}
}
